from os.path import split
from threading import Thread
from select import select
from cStringIO import StringIO

from BitTornado.__init__ import version_short
from BitTornado.bencode import bencode, bdecode
from LMG.Utility.helpers import to_utf8
from LMG.Utility.constants import *

try:
    from webframework import RequestHandler, app_get
    WEBSERVICE = True
except:
    WEBSERVICE = False
    class RequestHandler:
        pass
    def app_get(*args, **kwargs):
        pass
            
class MainRequestHandler(RequestHandler):
    """Handles command requests"""
    def get(self, *args):
        # Get Action
        params = self.request.parameters()
        action = params.get('action')
        # Invalid Action
        if not action or isinstance(action, list):
            self.response.out.write(bencode({"error": "Invalid action"}))
        # Quit LH-ABC
        elif action == "quit":
            utility.webserver.command(action, params)
            utility.webserver.stop()
        # Close WebServer
        elif action == "close": 
            utility.webserver.stop()
        # Other command
        else:
            response = utility.webserver.command(action, params)
            self.response.out.write(bencode(to_utf8(response)))

    def post(self, *args):
        params = self.request.parameters()
        action = params.get('action')
        if action != "addfile":
            return
        
        environ = self.request.environ
        sock = environ['wsgi.input']
        length = int(environ["CONTENT_LENGTH"])
        data = StringIO()
        write = False

        endLine = None
        contentType = None
        while length:
            # Read line
            line = sock.readline()
            length -= len(line)
            # Header
            if not write:
                if line.startswith("---"):
                    endLine = line
                elif line.startswith("Content-Type:"):
                    contentType = line.split(" ")[1]
                else:
                    write = line == '\r\n'
                continue
            # Data
            if contentType and not contentType.startswith("application/x-bittorrent"):
                break
            if line in endLine:
                break
            data.write(line)
        torrent = None
        try:
            torrent = bdecode(data.getvalue(), sloppy = 1)
        finally:
            data.close()
        if torrent:
            utility.queue.addTorrentFromMetainfoCallback(torrent, caller = "web")
        
form = """
<form action="../cmd?action=addfile"
enctype="multipart/form-data" method="post">
<p>
Please specify a torrent file: <input type="file" name="datafile" size="40"> <input type="submit" value="Send" size="40">
</p>
</form>
"""
class MainGuiHandler(RequestHandler):
    """Handles gui requests"""
    def get(self, *args):
        # Get page
        params = self.request.parameters()
        page = params.get('page')
        if page is None:
            page = "main"
            
        # Invalid page
        if not page or isinstance(page, list):
            self.response.out.write(bencode({"error": "Invalid page"}))
        # Main Page
        elif page == "main":
            s = self.response.out
            # head
            s.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' \
                '<html><head><title>LH-ABC Web Service</title>\n')
            s.write('</head>\n<body>\n' \
                '<h3>LH-ABC Web Service</h3>\n'\
                '<ul>\n'
                '<li><strong>Web Service Version:</strong> 5.0</li>\n\n')
            s.write(form)
            # query
            query = utility.webserver.command("query", {})
            s.write('<table summary="transfers" border="1">\n')
            # columns
            s.write('<tr>')
            for column in query['columns']:
                s.write('<th>%s</th>' %(column))
            s.write('<th>infohash</th>')
            s.write('</tr>\n')
            # table
            for torrent_hash in query['torrents']:
                s.write('<tr>')
                for val in query['torrents'][torrent_hash]:
                    s.write('<td>%s</td>' % (val or "---"))
                s.write('<td><code>%s</code></td>' % (torrent_hash))
                s.write('</tr>\n')

        # Unknown page
        else:
            self.response.out.write(bencode({"error": "Unknown page"}))
                    
class WebService(object):
    """Web server"""
    def __init__(self):
        """Initialize the web service"""
        utility.commands = CommandHandler()
        utility.webserver = self
        self.webdlg = None
        self.active = False
        self.server = app_get([('/cmd', MainRequestHandler),
                               ('/gui', MainGuiHandler)],
                              host = utility.webconfig.Read('host'),
                              port = utility.webconfig.Read('port', "int"),
                              )

        if utility.webconfig.Read("webautostart", "boolean"):
            self.start()
            
    def command(self, cmd, parameters):
        return utility.commands.action(cmd, parameters)
        
    def stop(self):
        """Stop serving"""
        # Stop server
        self.server.DoStop()
        # Update labels
        self.active = False
        self.updateLabels()

    def start(self):
        if self.active:
            return
        # Update labels
        self.active = True
        self.updateLabels()
        # Activate server
        self.server.ChangeAddress((utility.webconfig.Read('host'),
                                   utility.webconfig.Read('port', "int")))
        self.server.DoStart()
        # Start serving
        t = Thread(target = self.run)
        t.setDaemon(False)
        t.start()
        
    def run(self):
        """Start serving"""
        while self.active:
            r, w, e = select([self.server], [], [], 5)
            if r:
                try:
                    self.server.handle_request()
                except:
                    pass
                
    def updateLabels(self):       
        utility.actions[ACTION_WEBSERVICE].updateButton()
        if self.webdlg is not None:
            self.webdlg.updateLabels()
        
class CommandHandler(object):
    def __init__(self):
        # Mapping of which methods should be called for each command
        self._command_mappings = {"quit": self._quit,
                                  "version": self._version,
                                  
                                  "query": self._query,
                                  "getfiles": self._getfiles,
                                  "setfileprio": self._setfileprio,
                                  
                                  "getsettings": self._getsettings,
                                  "setsettings": self._setsettings,
                                  
                                  "getprops": self._getprops,
                                  "setprops": self._setprops,
                                  
                                  "addurl": self._addurl,
                                  "addfile": self._addfile,
                                  
                                  "remove": self._remove,
                                  "removedata": self._removedata,
                                  
                                  "start": self._start,
                                  "pause": self._pause,
                                  "stop": self._stop,
                                  "queue": self._queue,
                                  "recheck": self._recheck,
                                  "superseed": self._superseed,
                                  
                                  "setpriority": self._setpriority,

                                  "move": self._move,
                                  }
        
    def __list(self, v):
        if not isinstance(v, list):
            return [v]
        return v
    
    def __int(self, v):
        try:
            return int(v)
        except:
            return None

    def __str(self, v):
        if isinstance(v, list):
            return v[0]
        return v
    
    def getTorrentsFromCommand(self, parameters):
        """ Get torrents from parameters """
        torrents = []
        if "hash" in parameters:
            hash_values = self.__list(parameters["hash"])
            for infohash in hash_values:
                torrent = utility.queue.getTorrent(info_hash = infohash)
                if torrent:
                    torrents.append(torrent)
        return torrents

    def action(self, command, parameters):
        """ Perform action """
        # Premission
        if not utility.webconfig.Read('allow_' + command, "boolean"):
            return self._error_permission()
        
        # Action
        try:
            func = self._command_mappings[command]
            return func(parameters)
        except KeyError:
            pass
        
        # Invalid
        return self._error_invalid()
    
    def _error_permission(self):
        return {"error": "Permission denied"}
    
    def _error_invalid(self):
        return {"error": "Invalid request"}

    def _quit(self, parameters):
        """ Quit LH-ABC """
        feedback = {}
        utility.frame.OnCloseWindow(silent = True)
        return feedback

    def _version(self, parameters):
        """ Get current version """
        feedback = {}
        feedback['result'] = version_short
        return feedback
    
    def _query(self, parameters):
        """ Query torrents """
        feedback = {}

        # Target torrents
        targets = self.getTorrentsFromCommand(parameters)
        if not targets:
            targets = utility.torrents['all']
            
        # Columns
        columns = []
        for colid in xrange(utility.window.list.columns.minid, utility.window.list.columns.maxid):
            columns.append(utility.window.list.columns.getTextfromID(colid))
        feedback['columns'] = columns

        # Torrents
        torrents = {}
        for torrent in targets:            
            torrent_result_columns = []
            for colid in xrange(utility.window.list.columns.minid, utility.window.list.columns.maxid):
                torrent_result_columns.append(torrent.getColumnText(colid, force = True))
            torrents[torrent.infohash] = torrent_result_columns
        feedback['torrents'] = torrents
        
        return feedback

    def _getfiles(self, parameters):
        """ Query torrent file info """
        feedback = {}
        
        # Target torrents
        targets = self.getTorrentsFromCommand(parameters)
        files = {}
        for torrent in targets:
            fileslist = []
            # Single file
            if torrent.files.isFile():
                file = [split(torrent.files.getProcDest(pathonly = False, checkexists = False))[1], #file
                        torrent.info['length'],                                                     #size
                        torrent.getColumnValue(COL_PROGRESS),                                       #progress
                        1]
                fileslist.append(file)
            # Multi file
            else:
                priorities = torrent.files.filepriorities
                xfiles = torrent.info['files']
                for index in range(len(xfiles)):
                    file = xfiles[index]
                    fileslist.append([file.get('path', [""])[-1],                                   #file
                                      file['length'],                                               #size
                                      torrent.files.fileprogress[index],                            #progress          
                                      priorities[index]]                                            #priority
                                     )
            files[torrent.infohash] = fileslist
        feedback['result'] = files
        
        return feedback
    
    def _setfileprio(self, parameters):
        """ Set file priority """
        feedback = {}

        # Target torrents
        targets = self.getTorrentsFromCommand(parameters)
        if len(targets) != 1:
            return self._error_invalid()

        # Get priority
        priority = self.__int(parameters.get('p'))
        if priority is None or priority > 2 or priority < -1:
            return self._error_invalid()

        # Get files
        files = self.__list(parameters.get('f', []))
        if not files:
            return self._error_invalid()

        # Set priorities
        torrent = targets[0]
        priorities = torrent.files.filepriorities[:]
        for index in files:
            index = self.__int(index)
            if index is not None:
                priorities[index] = priority
        torrent.files.setFilePriorities(priorities)
        
        return feedback

    def _getsettings(self, parameters):
        """ Get settings """
        feedback = {}

        # Get keys
        keys = self.__list(parameters.get('s', []))

        # Read settings
        Read = utility.config.Read
        settings = {}
        for key in keys:
            settings[key] = Read(key)
        feedback['result'] = settings
        return feedback

    def _setsettings(self, parameters):
        """ Set settings """
        feedback = {}
        # Get Keys
        keys = self.__list(parameters.get('s', []))
        # Get Values
        values = self.__list(parameters.get('v', []))
        if len(keys) != len(values):
            return self._error_invalid()
        # Write settings
        for index in xrange(len(keys)):
            utility.config.Write(keys[index], values[index])
        config.Flush()  
        return feedback
    
    def _remove(self, parameters):
        """ Remove torrent """
        target = self.__str(parameters.get("target", ""))
        if target == "completed":
            return utility.actions[ACTION_CLEARALL].command(parameters)
        return utility.actions[ACTION_REMOVE].command(parameters)

    def _removedata(self, parameters):
        """ Remove torrent and data """
        return utility.actions[ACTION_REMOVEFILE].command(parameters)

    def _start(self, parameters):
        """ Resume torrent """
        return utility.actions[ACTION_RESUME].command(parameters)
    
    def _pause(self, parameters):
        """ Pause torrent """
        return utility.actions[ACTION_PAUSE].command(parameters)
    
    def _stop(self, parameters):
        """ Stop torrent """
        target = self.__str(parameters.get("target", ""))
        if target == "all":
            utility.actions[ACTION_STOPALL].command(parameters)
        return utility.actions[ACTION_STOP].command(parameters)
    
    def _queue(self, parameters):
        """ Queue torrent """
        target = self.__str(parameters.get("target", ""))
        if target == "stopped":
            return utility.actions[ACTION_UNSTOPALL].command(parameters)
        if target == "active":
            return utility.actions[ACTION_QUEUEACTIVE].command(parameters)
        return utility.actions[ACTION_QUEUE].command(parameters)
        
    def _recheck(self, parameters):
        """ Force Hash Check """
        return utility.actions[ACTION_HASHCHECK].command(parameters)
    
    def _superseed(self, parameters):
        """ Set super seed """
        return utility.actions[ACTION_SUPERSEED].command(parameters)
        
    def _setpriority(self, parameters):
        """ Change torrent priority """
        feedback = {}
        targets = self.getTorrentsFromCommand(parameters)
        priority = self.__int(parameters.get("p"))
        if priority is None:
            return self._error_invalid()
        if not 0 <= priority < 4:
            return self._error_invalid()            
        for torrent in targets:
            torrent.changePriority(priority)
        return feedback
    
    def _move(self, parameters):
        """ Move in list """
        feedback = {}
        direction = self.__str(parameters.get("dir"))    
        move = {"up": ACTION_MOVEUP, "down": ACTION_MOVEDOWN, "top": ACTION_MOVETOP, "bottom": ACTION_MOVEBOTTOM}
        if direction in move:
            return utility.actions[move[direction]].command(parameters)
        return self._error_invalid()
    
    def _addurl(self, parameters):
        """ Add torrent from URL """
        feedback = {}
        url = self.__str(parameters.get("s", ""))
        cookies = self.__str(parameters.get("c", ""))
        utility.queue.addtorrents.AddTorrentURL(url, caller="web", cookies = cookies)
        return feedback

    def _getprops(self, parameters):
        """ Get torrent properties """
        feedback = {}
        # Target torrents
        targets = self.getTorrentsFromCommand(parameters)
        # Target properties
        props = parameters.get("s", None)
        # Get local info
        for torrent in targets:
            info = torrent.connection.getLocalInfo(props)
            feedback[torrent.infohash] = info
        return feedback

    def _setprops(self, parameters):
        """ Set torrent properties """
        feedback = {}
        # Target torrents
        targets = self.getTorrentsFromCommand(parameters)
        # Get parameters
        keys = parameters.get("s", [])
        vals = parameters.get("v", [])
        if len(keys) != len(vals):
            return self._error_invalid()
        info = {}
        for index in xrange(len(keys)):
            info[keys[index]] = vals[index]
        # Set settings
        for torrent in targets:
            torrent.connection.changeLocalInfo(info)

        return feedback

    # TODO: implement add file
    def _addfile(self, parameters):
        """ Add torrent from File """
        feedback = {}
        return feedback
